/*
 * YAFFS: Yet Another Flash File System. A NAND-flash specific file system.
 *
 * Copyright (C) 2002-2011 Aleph One Ltd.
 *   for Toby Churchill Ltd and Brightstar Engineering
 *
 * Created by Charles Manning <charles@aleph1.co.uk>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

/*
 * This provides a YAFFS nand emulation on a file for emulating 2kB pages.
 * This is only intended as test code to test persistence etc.
 */

#include "yportenv.h"
#include "yaffs_trace.h"

#include "yaffs_flashif2.h"
#include "yaffs_guts.h"
#include "yaffs_fileem2k.h"
#include "yaffs_packedtags2.h"


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#if 1

#define SIZE_IN_MB (512)
#define PAGE_DATA_SIZE (2048)
#define PAGE_SPARE_SIZE  (64)
#define PAGE_SIZE  (PAGE_DATA_SIZE + PAGE_SPARE_SIZE)
#define PAGES_PER_BLOCK (64)
#define BLOCK_DATA_SIZE (PAGE_DATA_SIZE * PAGES_PER_BLOCK)
#define BLOCK_SIZE (PAGES_PER_BLOCK * (PAGE_SIZE))
#define BLOCKS_PER_MB ((1024*1024)/BLOCK_DATA_SIZE)
#define SIZE_IN_BLOCKS (BLOCKS_PER_MB * SIZE_IN_MB)
#define BLOCKS_PER_CHIP (((SIZE_IN_MB*1024)/BLOCK_DATA_SIZE)*1024)

#else

#define SIZE_IN_MB 128
#define PAGE_DATA_SIZE (512)
#define SPARE_SIZE  (16)
#define PAGE_SIZE  (PAGE_DATA_SIZE + SPARE_SIZE)
#define PAGES_PER_BLOCK (32)
#define BLOCK_DATA_SIZE (PAGE_SIZE * PAGES_PER_BLOCK)
#define BLOCK_SIZE (PAGES_PER_BLOCK * (PAGE_SIZE))
#define BLOCKS_PER_MB ((1024*1024)/BLOCK_DATA_SIZE)
#define SIZE_IN_BLOCKS (BLOCKS_PER_MB * SIZE_IN_MB)

#endif

extern int fsl_nand_init(void);
extern int fsl_nand_erase_block(int block);
extern int fsl_nand_read_page(int page, unsigned char *data, int data_len, unsigned char *oob, int oob_len);
extern int fsl_nand_write_page(int page, unsigned char *data, int data_len, unsigned char *oob, int oob_len);

char *strdup(const char *s)
{
	long len;
	char *buf;

	if (!s)
		return NULL;

	len = strlen(s) + 1;
	buf = malloc(len);
	if (buf)
		memcpy(buf, s, len);
	return buf;
}

static int yflash2_WriteChunk(struct yaffs_dev *dev, int nand_chunk,
				   const u8 *data, int data_len,
				   const u8 *oob, int oob_len)
{
	u8 spare[PAGE_SPARE_SIZE];

	yaffs_trace(YAFFS_TRACE_DEBUG,"%s: nand_chunk %d, data %p, data_len %d, oob %p, oob_len %d",
			__FUNCTION__, nand_chunk, data, data_len, oob, oob_len);

	memset(spare, 0xFF, PAGE_SPARE_SIZE);

	if (oob) {
		memcpy(&spare[1], oob, oob_len);
	}

	fsl_nand_write_page(nand_chunk, (u8 *)data, data_len, (u8 *)spare, PAGE_SPARE_SIZE);

	return YAFFS_OK;
}

static int yflash2_ReadChunk(struct yaffs_dev *dev, int nand_chunk,
				   u8 *data, int data_len,
				   u8 *oob, int oob_len,
				   enum yaffs_ecc_result *ecc_result)
{
	u8 spare[PAGE_SPARE_SIZE];

	yaffs_trace(YAFFS_TRACE_DEBUG,"%s: nand_chunk %d, data %p, data_len %d, oob %p, oob_len %d",
				__FUNCTION__, nand_chunk, data, data_len, oob, oob_len);

	memset(spare, 0, PAGE_SPARE_SIZE);

	fsl_nand_read_page(nand_chunk, data, data_len, spare, PAGE_SPARE_SIZE);	

	if (oob) {
		memcpy(oob, &spare[1], oob_len);
	}

	if (ecc_result)
		*ecc_result = YAFFS_ECC_RESULT_NO_ERROR;

	return YAFFS_OK;
}

static int yflash2_EraseBlock(struct yaffs_dev *dev, int block_no)
{
	if(block_no < 0 || block_no >= BLOCKS_PER_CHIP)
	{
		yaffs_trace(YAFFS_TRACE_DEBUG,"Attempt to erase non-existant block %d",block_no);
		return YAFFS_FAIL;
	}
	else
	{
		yaffs_trace(YAFFS_TRACE_DEBUG,"%s: block %d", __FUNCTION__, block_no);
		fsl_nand_erase_block(block_no);
		return YAFFS_OK;
	}
}

static int yflash2_MarkBad(struct yaffs_dev *dev, int block_no)
{
	u8 spare[PAGE_SPARE_SIZE];

	yaffs_trace(YAFFS_TRACE_DEBUG,"%s: block %d", __FUNCTION__, block_no);

	memset(spare, 0, PAGE_SPARE_SIZE);
	fsl_nand_write_page(block_no*PAGES_PER_BLOCK, NULL, 0, (u8 *)spare, PAGE_SPARE_SIZE);
	fsl_nand_write_page(block_no*PAGES_PER_BLOCK + 1, NULL, 0, (u8 *)spare, PAGE_SPARE_SIZE);

	return YAFFS_OK;
}

static int yflash2_CheckBad(struct yaffs_dev *dev, int block_no)
{
	int block_bad = 0;
	u8 spare[PAGE_SPARE_SIZE];

	memset(spare, 0, PAGE_SPARE_SIZE);
	fsl_nand_read_page(block_no*PAGES_PER_BLOCK, NULL, 0, (u8 *)spare, PAGE_SPARE_SIZE);
	if (spare[0] != 0xFF) {
		block_bad = 1;
	}

	memset(spare, 0, PAGE_SPARE_SIZE);
	fsl_nand_read_page(block_no*PAGES_PER_BLOCK + 1, NULL, 0, (u8 *)spare, PAGE_SPARE_SIZE);
	if (spare[0] != 0xFF) {
		block_bad = 1;
	}

	yaffs_trace(YAFFS_TRACE_DEBUG,"%s: block %d, block_bad %d", __FUNCTION__, block_no, block_bad);

	if (block_bad) {
		return YAFFS_FAIL;
	}

	return YAFFS_OK;
}

static int yflash2_Initialise(struct yaffs_dev *dev)
{
	(void) dev;

	yaffs_trace(YAFFS_TRACE_DEBUG,"%s", __FUNCTION__);

	fsl_nand_init();

	return YAFFS_OK;
}

struct yaffs_dev *yflash2_install_drv(const char *name)
{
	struct yaffs_dev *dev = NULL;
	struct yaffs_param *param;
	struct yaffs_driver *drv;

	dev = malloc(sizeof(*dev));

	if(!dev)
		return NULL;

	memset(dev, 0, sizeof(*dev));

	dev->param.name = strdup(name);

	if(!dev->param.name) {
		free(dev);
		return NULL;
	}

	drv = &dev->drv;

	drv->drv_write_chunk_fn = yflash2_WriteChunk;
	drv->drv_read_chunk_fn = yflash2_ReadChunk;
	drv->drv_erase_fn = yflash2_EraseBlock;
	drv->drv_mark_bad_fn = yflash2_MarkBad;
	drv->drv_check_bad_fn = yflash2_CheckBad;
	drv->drv_initialise_fn = yflash2_Initialise;

	param = &dev->param;

	param->total_bytes_per_chunk = PAGE_DATA_SIZE;
	param->chunks_per_block = PAGES_PER_BLOCK;
	param->start_block = 0;
	param->end_block = BLOCKS_PER_CHIP-1;
	param->is_yaffs2 = 1;
	param->use_nand_ecc = 1;
	param->always_check_erased = 1;
	param->inband_tags = 1;

	param->n_reserved_blocks = 5;
	param->wide_tnodes_disabled=0;
	param->refresh_period = 1000;
	param->n_caches = 10; // Use caches

	param->enable_xattr = 1;

	/* dev->driver_context is not used by this simulator */

	yaffs_add_device(dev);

	return dev;
}

